package com.qn.service.impl;

import com.qn.config.websocket.WebSocketServer;
import com.qn.dao.CompanydataDao;
import com.qn.entity.DueOutMail;
import com.qn.entity.MailSendStatus;
import com.qn.entity.Sendnumbers;
import com.qn.service.MailPushService;
import com.qn.utils.MailUtils;
import com.qn.utils.PoolSend;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author olo
 * @description 邮件推送服务实现类
 */
@Service
@Slf4j
public class EmailPushServiceImpl implements MailPushService {

    /**
     * WebSocket控制器,在这里主要向前端响应发送的队列状态
     */
    @Resource
    WebSocketServer webSocketServer;
    /**
     * 企业数据接口
     */
    @Resource
    private CompanydataDao companydataDao;

    /**
     * 发件人集合
     */
    private List<Sendnumbers> sendEmailList;

    /**
     * 静态变量，存放待发送邮件
     */
    Queue<DueOutMail> mailSendQueue = new LinkedList<>();

    /**
     * 存放所有发送成功的邮件
     */
    List<String> successList = new ArrayList<>();

    /**
     * 存放所有发送失败的邮件
     */
    List<String> failList = new ArrayList<>();

    /**
     * 在这个服务里都响应给 sendQueue
     */
    private final String resTo = "sendQueue";

    @Resource
    private MailUtils mailUtils;

    /**
     * 管理员邮箱
     */
    private final String[] adminEmail = {"3500412648@qq.com", "lin986823307@foxmail.com", "2111837350@qq.com"};

    /**
     * <h1>① 前端请求,将发送对象信息添加到待发送的队列</h1>
     */
    @Override
    public void pushSendQueue(DueOutMail mail) {
        if (mailSendQueue.offer(mail)) {
            log.info("----*邮件发送对象已添加至发送队列*----");
            for (DueOutMail dueOutMail : mailSendQueue) {
                log.info("邮件标题:" + dueOutMail.getTitle() + "\t集合长度:" + dueOutMail.getList().size() + "\t开始时间:" + dueOutMail.getBeginDate());
            }
            executeQueue();
        } else {
            log.info("----*邮件发送对象添加队列时出现异常*----");
        }
    }

    /**
     * <h1>② 开启队列,并执行队列</h1>
     */
    public void executeQueue() {
        //判断队列的长度是否大于0
        if (mailSendQueue.size() > 0) {
            //获取队列第一个元素
            DueOutMail mails = mailSendQueue.peek();
            //判断第一个队列是否正在发送
            if (mails.getMailSendStatus() == null) {
                //调用发送邮件
                sendMail(mails.getList(), mails.getTitle(), mails.getMessage());
            } else {
                //响应当前队列
                webSocketServer.sendThisUser(resTo, mailSendQueue);
                log.info("队列[" + mails.getBeginDate() + "]需要等待上一个队列发送完毕");
            }
        } else {
            webSocketServer.sendThisUser(resTo, mailSendQueue);
            log.info("队列已完成");
        }
    }

    /**
     * <h1>③ 装载并启动线程开始发送邮件</h1>
     *
     * @param list    收件人集合
     * @param message 邮件的内容
     * @param title   邮件的标题
     */
    public void sendMail(List<String> list, String title, String message) {
        //查看需要发送的邮件数量
        int size = list.size();
        //param1 为初始线程  param2为最大线程
        PoolSend poolSend = new PoolSend(30, size > 30 ? size : 50);
        //给首位队列初始化状态对象
        Objects.requireNonNull(mailSendQueue.peek()).setMailSendStatus(new MailSendStatus(0, 0, new ArrayList<>(), new ArrayList<>(), false, size));
        //通知管理员,有新的队列开始发送啦
        notifyAdmin("开始发送队列", mailSendQueue.peek());
        //响应队列 将消息发送给指定的客户端
        webSocketServer.sendThisUser(resTo, mailSendQueue);
        for (int i = 0; i < size; i++) {
            int finalI = i;
            poolSend.send(() -> {
                //调用邮件线程任务
                String threadName = String.valueOf(finalI);
                mailThreadTask(threadName, list, title, message);
                //如果线程名称等于长度-1,则关闭线程
                if ((size - 1) == Integer.parseInt(threadName)) {
                    new Thread(poolSend::closeThread).start();
                }
            });
        }
        poolSend.poolWait2();
        //重置状态
        resetSendData();
        //删除首位队列,准备下一个队列发送
        notifyAdmin("队列发送完毕!!!", mailSendQueue.poll());
        //回收垃圾
        System.gc();
        //回调发送下一个队列
        executeQueue();
    }

    /**
     * 队列发送完毕,向管理员发送队列结果
     *
     * @description TODO
     * @author olo
     * @date 2021/10/23
     */
    private void notifyAdmin(String title, DueOutMail dueOutMails) {
        log.info("正在通知管理员");
        for (String email : adminEmail) {
            mailUtils.serverSend(email, title, String.format("<h1>当前剩余队列:%s-------[总量:%s | 成功:%s | 失败%s]</h1></br>%s", mailSendQueue.size(), dueOutMails.getList().size(), dueOutMails.getMailSendStatus().getSuccessList().size(), dueOutMails.getMailSendStatus().getFailList().size(), dueOutMails.getMessage()));
        }
    }

    /**
     * 邮件线程任务
     *
     * @param threadName 线程名称
     * @param list       发送集合
     * @param title      邮件标题
     * @param message    邮件内容
     */
    private void mailThreadTask(String threadName, List<String> list, String title, String message) {
        //================获得当前线程,设置线程名称==============
        Thread thread = Thread.currentThread();
        thread.setName(threadName);
        //===============发送邮件===============
        String currentSendMail = list.get(Integer.parseInt(threadName));
        //记录发送结果
        Boolean sendResult;
        //程序进行发送
        sendResult = branchSend(currentSendMail, title, message);
        //==============对第一次发送结果做点小处理==============
        if (!sendResult) {
            //第一次发送失败后则再用服务器代理发送
            sendResult = mailUtils.serverSend(currentSendMail, title, message);
        }
        if (sendResult) {
            //发送成功时的处理
            mailSendSuccessHandler(currentSendMail);
        } else {
            //第二次邮件还是发送失败就放弃这个邮件
            //发送失败时的处理
            mailSendFailHandler(currentSendMail);
        }
        //响应队列
        webSocketServer.sendThisUser(resTo, mailSendQueue);
    }

    /**
     * <h1>④ 发送方式分支</h1>
     *
     * @param receiveEmail 收件人邮箱
     * @param mailTitle    邮件标题
     * @param mailContent  邮件内容
     * @return 是否发送成功
     * @date 2021/10/17 1:40
     */
    @Override
    public Boolean branchSend(String receiveEmail, String mailTitle, String mailContent) {
        //如果为null则说明是第一次调用,需从数据库进行获取
        if (sendEmailList == null) {
            sendEmailList = getSendEmails(null);
        }
        if (sendEmailList.size() > 0) {
            return mailUtils.sendMail(sendEmailList.get((int) (Math.random() * sendEmailList.size())), receiveEmail, mailTitle, mailContent);
        }
        //获取完后如果还是为空则让服务器进行发送
        return mailUtils.serverSend(receiveEmail, mailTitle, mailContent);
    }

    /**
     * 邮件发送成功的操作
     */
    private void mailSendSuccessHandler(String mail) {
        successList.add(mail);
        //给响应状态进行设值
        Objects.requireNonNull(mailSendQueue.peek()).getMailSendStatus().setSuccessList(mail);
        Objects.requireNonNull(mailSendQueue.peek()).getMailSendStatus().setMailSucceed(successList.size());
    }

    /**
     * 邮件发送失败的操作
     */
    private void mailSendFailHandler(String mail) {
        //将失败邮件存入失败邮件集合中
        failList.add(mail);
        //给响应状态进行设值
        Objects.requireNonNull(mailSendQueue.peek()).getMailSendStatus().setFailList(mail);
        Objects.requireNonNull(mailSendQueue.peek()).getMailSendStatus().setMailFail(failList.size());
    }

    /**
     * 获得收件人
     */
    @Override
    public List<Sendnumbers> getSendEmails(Integer id) {
        return companydataDao.getSendEmails(id);
    }

    /**
     * 删除收件人
     */
    @Override
    public Integer deleteThisSender(Integer id) {
        return companydataDao.deleteThisSender(id);
    }

    /**
     * 添加收件人
     */
    @Override
    public boolean addOneSender(Sendnumbers sendnumbers) {
        return companydataDao.addOneSender(sendnumbers) == 1;
    }

    /**
     * 修改收件人
     */
    @Override
    public boolean editThisSender(Sendnumbers sendnumbers) {
        return companydataDao.editThisSender(sendnumbers) == 1;
    }

    /**
     * 获得发送状态
     */
    @Override
    public Queue<DueOutMail> getMailSendQueue() {
        return mailSendQueue;
    }

    /**
     * 重置发送给状态
     */
    private void resetSendData() {
        successList.clear();
        failList.clear();
        log.info("状态重置成功!");
    }
}
